이전 과정에서 선택한 데이터들에서 각 포지션, 챔피언 별로 승리여부와 어떤 의미를 갖는지 탐색하며 확인하는 과정
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
# 경고메세지 무시
import warnings
warnings.filterwarnings('ignore')
# 피클로 처리해둔 데이터 읽기
import pickle
with open("dataV3_processed.pickle","rb") as fr:
df = pickle.load(fr)
df.head()
| gameDuration | assists | championName | damageDealtToObjectives | damageDealtToTurrets | damageSelfMitigated | deaths | dragonKills | kills | magicDamageDealtToChampions | ... | teamPosition | timeCCingOthers | totalDamageDealtToChampions | totalDamageShieldedOnTeammates | totalDamageTaken | totalHealsOnTeammates | totalMinionsKilled | trueDamageDealtToChampions | visionScore | win | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| matchId | |||||||||||||||||||||
| KR_5944529373 | 1548 | 2 | Trundle | 10809 | 8068 | 14206 | 4 | 1 | 4 | 2200 | ... | TOP | 16 | 17912 | 0 | 22268 | 0 | 199 | 1251 | 11 | False |
| KR_5944529373 | 1548 | 5 | Viego | 12425 | 647 | 13265 | 5 | 0 | 3 | 1112 | ... | JUNGLE | 14 | 10543 | 0 | 20618 | 0 | 37 | 799 | 24 | False |
| KR_5944529373 | 1548 | 1 | Katarina | 4894 | 0 | 13089 | 6 | 0 | 7 | 14299 | ... | MIDDLE | 1 | 19362 | 0 | 22430 | 0 | 142 | 426 | 20 | False |
| KR_5944529373 | 1548 | 2 | Tristana | 334 | 248 | 8841 | 6 | 0 | 5 | 1986 | ... | BOTTOM | 2 | 10721 | 0 | 19263 | 0 | 168 | 588 | 24 | False |
| KR_5944529373 | 1548 | 9 | Nautilus | 5165 | 0 | 21768 | 7 | 0 | 0 | 4585 | ... | UTILITY | 59 | 7656 | 536 | 19286 | 0 | 32 | 461 | 48 | False |
5 rows × 22 columns
챔피언 조합에 따른 승률 예측 모델'이라는 목표는 명확하지만, 주어진 데이터로 설정된 목표를 어떻게 달성할지는 현재로썬 명확하지 않은 상황이다.
당연하게도, 챔피언에 따른 각종 데이터의 변화와 승리여부에 따른 데이터의 변화가 확인되어야 한다.
또한, 포지션에 따른 챔피언별 게임플레이 내용이 다를것이 자명하므로 역시 확인해볼 필요성이 있다.
# 컬럼간 상관관계 히트맵으로 확인
plt.figure(figsize = (20,20))
sns.heatmap(df.corr(), annot=True, linewidth=1)
plt.show()
위의 히트맵을 보면 게임시간이 길어질수록 일부 컬럼의 값이 증가하는 양의 상관관계 확인 할 수 있다. 이는 자명한 결과로, 다른 컬럼간의 상관계수도 어떤 의미를 지닐 수 있다고 여길 수 있다.
자세히 살펴보면 몇몇 컬럼들이 강한 상관관계를 갖고 있으나, 유의미한 인사이트를 도출하기는 어렵다. 예를 들면 챔피언에게 가한 피해량(totalDamageDealtToChampions)과 킬수(kills)는 상관계수가 0.75로 강한 상관관계를 갖는다 볼 수 있으나, 이는 당연한 결론이며 이를 통해 새로운 결론이나 영감을 얻기는 힘들다.
반면에 승률 예측모델에서 타깃이라고 할 수 있는 종속변수인 승리여부(win)와 강한 상관관계를 갖는 컬럼은 없다. 오히려 승패여부보단 게임시간이 각 컬럼들의 값에 더 영향을 주는것을 확인 할 수 있다.
각 게임 내에서 챔피언의 플레이 내용과 스타일에 따라 나타나는 속성들의 관계가 상이할 것이라 예상했다. 예컨데 가한 피해량이 받은 피해량에 비해 훨씬 크다면 딜러로써, 그 반대의 경우라면 탱커로써의 역할을 수행했다고 해석이 가능하다.
이 가설에 따르면 같은 챔피언이라도 각 게임에서의 역할에 따라서 어떤 경우엔 탱커, 다른 경우엔 딜러로써 역할을 수행했다고 판단 할 수 있다. 또한 탱커들이 보여준 스탯과 딜러들이 보여준 스탯이 어느정도 명확히 구분이 될 것이라 예상했다.
위 내용을 시각화하여 해석을 해 본 결과는 아래와 같다.
# 받은 데미지와 가한 데미지 두 속성을 이용하여 데이터의 모든 행들을 플롯
sns.scatterplot(x=df['totalDamageDealtToChampions'], y=df['totalDamageTaken'], s=0.1)
plt.show()
위의 산점도를 확인한 결과, 예상과는 달리 탱커와 딜러 두 군집을 특정 지을 수 있는 경계를 발견 할 수 없었다.
각 집단의 정규성을 가정한다면, 전체 데이터는 각각 정규분포를 이루는 탱커와 딜러 두 정규분포의 합으로써 나타나야만 한다. 이를 확인해보고자, 받은피해량과 가한 피해량의 비율을 계산하여 분포를 시각화 하였다.
sns.histplot(data=np.log(df['totalDamageDealtToChampions']/df['totalDamageTaken']), binwidth=0.1)
plt.show()
우리의 경험에 의하면 분명 탱커와 딜러가 나타내는 통계량에는 유의미한 차이가 있어야만 한다. 하지만 가설과는 다르게 (가한피해량/받은피해량)의 비율값의 분포는 하나의 정규분포를 이룬다는 사실을 확인하였다. 가설이 틀리지 않았다면 접근방식이 잘못된 것이라 판단 할 수 밖에 없다.
이 오류에 대한 원인으로 추측되는 이유는 다음과 같다.
롤에 존재하는 챔피언의 수는 총 161가지로, 각 챔피언의 데이터를 하나의 샘플로 다룬다 하더라도 161개의 행이 있는것이라 볼 수 있다. 이 역시 충분히 정규성을 보여줄 수 있는 크기이기 때문에, 이를 하나의 모집단으로 해석한다면 각 챔피언들의 특성이 사라지고 하나의 정규분포처럼 관찰될수도 있다고 볼 수 있다.
위의 결론에 따르면 우선 데이터들을 각 챔피언별로 구분 해 볼 필요가 있다.
데이터를 챔피언명(championName)으로 그룹핑하며, 집계함수로는 평균값을 사용한 후 각 컬럼간 상관관계를 살펴보았다.
df_bychamp = df.groupby('championName').mean()
plt.figure(figsize = (20,20))
sns.heatmap(df_bychamp.corr(), annot=True, linewidth=1)
plt.show()
처음의 히트맵과 비교를 해보자면, 어느정도 상관관계를 보여주던 컬럼들간의 상관계수가 보다 뚜렷히 커진것을 확인 할 수 있다.
게임 내적으로 확연히 다른 챔피언이라고 인식되는 두 챔피언의 데이터를 통해 (가한피해량/받은피해량)의 관계를 위의 산점도로 다시 시각화한 내용은 아래와 같다.
챔피언: 케이틀린(딜러) vs 람머스(탱커)
df_Caitlyn = df[df['championName']=='Caitlyn']
df_Rammus = df[df['championName']=='Rammus']
df_sample = pd.concat([df_Caitlyn,df_Rammus])
sns.scatterplot(x=df_sample['totalDamageDealtToChampions'], y=df_sample['totalDamageTaken'],
hue=df_sample['championName'], s=1)
plt.show()
위 산점도를 통해 챔피언간 데이터의 분포가 명확히 구분됨을 확인 할 수 있었다.
챔피언간 통계량의 차이를 보다 자세히 살펴보기 위해 각 챔피언별로 몇몇 컬럼의 값의 분포를 살펴보았다.
위에서 살펴보았던 (가한피해량/받은피해량) 비율값의 히스토그램을 챔피언별로 나누어 시각화한 결과는 아래와 같다.
champList = df['championName'].value_counts().index # 챔피언 목록
# 챔프별로 tdd/tdt값 분포 히스토그램으로 확인
df['tdd/tdt'] = np.log(df['totalDamageDealtToChampions'] / df['totalDamageTaken'])
fig, ax = plt.subplots(ncols=13, nrows=13, figsize=(200,160))
for i in range(161):
sns.histplot(df[df['championName']==champList[i]]['tdd/tdt'], ax=ax[i//13,i%13],
binwidth=0.1, binrange=(-2,2)).set_title(champList[i])
plt.show()
(우클릭 -> 새 탭에서 이미지 열기로 크게 볼 수 있습니다.)
위의 히스토그램을 통해 한 챔피언 내에서의 (가한피해량/받은피해량) 비율값의 분포는 정규성을 띠고 있으며, 챔피언간의 평균의 차이는 명확히 드러남을 확인 할 수 있다.
위에서 예시로 보인 케이틀린과 람머스가 보이는 값의 차이를 비교하면 다음과 같다.
df[df['championName']=='Caitlyn']['tdd/tdt'].median(), df[df['championName']=='Rammus']['tdd/tdt'].median()
(0.04887487314122167, -0.6355153887170915)
딜러로 알려진 케이틀린의 비율값은 탱커로 알려진 람머스의 비율값보다 크다는 사실을 확인 할 수 있다.
딜러/탱커의 구분 외에도 게임 내적으로 챔피언의 성격을 구분짓는 몇가지 척도를 더 생각해 볼 수 있다. 마법/물리 데미지 비율은 널리 알려진 기준중 하나로, 유저들이 팀의 챔피언 구성을 고민하는데 있어서 중요하게 고려하는 요소 중 하나이다.
관찰한 통계량은 마법 피해량을 전체 피해량으로 나눈 값으로, 0에 가까울수록 물리피해 비중이 크고 1에 가까울수록 마법피해 비중이 큰 것이다.
위의 방식과 동일하게 확인한 결과는 다음과 같다.
df['pdratio'] = df['magicDamageDealtToChampions'] / df['totalDamageDealtToChampions']
fig, ax = plt.subplots(ncols=13, nrows=13, figsize=(200,160))
for i in range(161):
sns.histplot(df[df['championName']==champList[i]]['pdratio'], ax=ax[i//13,i%13],
binwidth=0.025, binrange=(0,1)).set_title(champList[i])
plt.show()
역시 챔피언에 따른 분포의 차이를 명확하게 확인 할 수 있다.
몇몇 챔피언의 경우 두 개의 봉우리를 갖는 분포를 보이는데, 이는 챔피언의 템빌드나 활용방식에 따라 한 챔피언이 두 가지의 서로 다른 특징을 보이는 것이라 해석 할 수 있다.
예시로 샤코의 히스토그램을 다시 살펴보면 아래와 같다.
sns.histplot(df[df['championName']=='Shaco']['pdratio'],
binwidth=0.025, binrange=(0,1)).set_title('Shaco')
plt.show()
두 봉우리가 각각 0.35, 0.85에서 나타나는것을 확인할 수 있다. 실제로 샤코는 게임 내에서 완전히 구분되는 두가지 다른 플레이 스타일이 가능한 챔피언이며, 그 사실을 통계적으로 확인 할 수 있다.
이와는 별개로 각 챔피언들간의 비율값의 분포를 살펴보면, 대부분의 경우 지나치게 1 또는 0 근처에 치우쳐 있음을 확인 할 수 있다. 이를 통해 해당 데이터는 bimodal한 분포를 보일 수 있다고 추론 할 수 있다. 실제로 일반적인 경우 게임 내적으로 챔피언들을 마법형/물리형으로 구분하며, 마법형 챔피언의 경우 가하는 물리데미지를 거의 무시하는 경우가 많다.
이를 확인하기 위해 해당 통계량으로 qq plot을 확인한 결과는 아래와 같다.
import scipy as sp
import scipy.stats
# 물리/마법 데미지 비율 qqplot
fig = plt.figure(figsize = (15,10))
ax = fig.add_subplot(1,1,1)
sp.stats.probplot(df['pdratio'], plot=plt)
ax.set_ylim(0,1)
(0.0, 1.0)
위의 그래프는 데이터가 전형적인 bimodal한 분포를 보이는것으로 볼 수 있다. 이를 통해 우리는 각 챔피언들을 피해량의 유형을 통해 물리형/마법형 챔피언으로 분류가 가능함을 판단할 수 있다.
챔피언에 따른 또다른 특징으로, 받아내는 피해량을 감소시키는 유형이 있을 수 있다. 이러한 챔피언들의 경우, 받아내는 피해를 챔피언 고유의 스킬이나 각종 아이템으로 감소시키는 능력이 뛰어나다.
(받은 피해량/경감시킨 피해량)의 비율값 역시 챔피언별로 상이할 것이라 예상하고, 마찬가지로 히스토그램을 확인하였다.
df['tdt/dsm'] = np.log(df['totalDamageTaken'] / df['damageSelfMitigated'])
fig, ax = plt.subplots(ncols=13, nrows=13, figsize=(200,160))
for i in range(161):
sns.histplot(df[df['championName']==champList[i]]['tdt/dsm'], ax=ax[i//13,i%13],
binwidth=0.05, binrange=(-1,1)).set_title(champList[i])
plt.show()
위와 같이 챔프별로 상이한 분포를 갖는것을 확인할 수 있다. 값의 범위가 0.25이상 1이하인 경우 딜링 위주의 아이템구성을 갖는 챔피언이고, -1이상 -0.25이하인 경우 탱킹 위주의 아이템구성, -0.25이상 0.25이하인 경우 탱템과 딜템을 적절하게 섞는 구성을 갖는 챔피언이라 해석할 수 있다.
앞선 가설처럼 우리는 다양한 관점에서 챔피언에 따른 각종 통계량의 변화를 관찰할 수 있었다. 데이터 탐색이라는 과정을 통해 우리가 가지고 있는 데이터에 일련의 의미가 숨어있을 것이라는 확신을 가지고 앞으로의 분석을 진행 할 수 있게 되었다.